// linux/kernel/vsprintf.c 
#include <string.h>

typedef char *va_list;

#define va_rounded_size(TYPE) \
	(((sizeof(TYPE) + sizeof(int)-1)/sizeof(int)) * sizeof(int))

#define va_start(AP, LASTARG) \
	(AP = ((char *) &(LASTARG) + va_rounded_size(LASTARG)))
	 
#define va_arg(AP, TYPE) 	\
	(AP += va_rounded_size(TYPE), \
	*((TYPE *) (AP - va_rounded_size(TYPE))) )
	 
#define va_end(AP)
	 
#define is_digit(c) ((c)>='0'&&(c)<='9')

void print(const char *);
extern unsigned long dwDispPos;
extern unsigned char bDispColor;

static int skip_atoi(const char **s)
{
	int i=0;
	while(is_digit(**s))
		i = i*10 + *((*s)++)-'0';
	return i;
}

#define ZEROPAD		1
#define SIGN		2
#define PLUS		4
#define SPACE		8
#define LEFT		16
#define SPECIAL		32
#define	SMALL		64

#define do_div(num,base) ({\
		int _res;\
		__asm__("divl %4"\
			:"=a"(num),"=d"(_res)\
			:"a"(num), "d"(0),"b"(base)\
		       );\
		_res;\
		})

static char* number(char *str, int num, int base, int size, int precision, int type)
{
	char c, sign, tmp[36];
	const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	const char *small_digits = "0123456789abcdefghijklmnopqrstuvwxyz";
	int i;
	if(type&SMALL) digits = small_digits;
	if(type&LEFT) type &= ~ZEROPAD;
	if(base<2||base>36) return 0;
	c = (type&ZEROPAD)?'0':' ';
	if(type&SIGN&&num<0) {
		sign = '-';
		num = -num;
	}else
		sign = (type&PLUS)?'+':((type&SPACE)?' ':0);
	if(sign) size--;
	if(type&SPECIAL) {
		if(base==16) size -= 2;
		else if(base==8) size--;
	}
	i = 0;
	if(num==0) 
		tmp[i++] = '0';
	else while(num!=0)
		tmp[i++] = digits[do_div(num,base)];
	if(i>precision) precision = i;
	size -= precision;
	if(!(type&(ZEROPAD+LEFT)))
		while(size-- > 0)
			*str++ = ' ';
	if(sign)
		*str++ = sign;
	if(type&SPECIAL){
		if(base==8)
			*str++ = '0';
		else if(base==16){
			*str++ = '0';
			*str++ = digits[33];
		}
	}
	if(!(type&LEFT))
		while(size-- > 0)
			*str++ = c;
	while(i<precision--)
		*str++ = '0';
	while(i-- > 0)
		*str++ = tmp[i];
	while(size-- > 0)
		*str++ = ' ';
	return str;
}

static int vsprintf(char *buf, const char *fmt, va_list args)
{
	int len;
	int i;
	char *str;
	char *s;
	int *ip;
	int flags;
	int field_width;
	int precision;
	int qualifier;
	for(str=buf; *fmt; ++fmt)
	{
		if(*fmt!='%'){
			*str++ = *fmt;
			continue;
		}
		flags = 0;
		repeat:
			fmt++;
			switch(*fmt){
				case'-':flags|=LEFT;goto repeat;
				case'+':flags|=PLUS;goto repeat;
				case' ':flags|=SPACE;goto repeat;
				case'#':flags|=SPECIAL;goto repeat;
				case'0' :flags|=ZEROPAD;goto repeat;
			}
			field_width = -1;
			if(is_digit(*fmt))
				field_width = skip_atoi(&fmt);
			else if(*fmt == '*') {
				field_width = va_arg(args, int);
				if(field_width<0){
					field_width = -field_width;
					flags|=LEFT;
				}
			}
			precision = -1;
			if(*fmt=='.') {
				fmt++;
				if(is_digit(*fmt))
					precision = skip_atoi(&fmt);
				else if(*fmt == '*') {
					precision = va_arg(args, int);
				}
				if (precision < 0)
					precision = 0;
			}
			qualifier = -1;
			if(*fmt=='h'||*fmt=='l'||*fmt=='L') {
				qualifier = *fmt;
				fmt++;
			}
			switch(*fmt){
				case'c':
					if(!(flags&LEFT))
						while(--field_width>0)
							*str++ = ' ';
					*str++ = (unsigned char)va_arg(args,int);
					while(--field_width>0)
						*str++ = ' ';
					break;
				case's':
					s = va_arg(args, char*);
					len = strlen(s);
					if(precision<0)
						precision = len;
					else if(len > precision)
						len = precision;
					if(!(flags&LEFT))
						while(len<field_width--)
							*str++=' ';
					for(i=0;i<len;i++)
						*str++ = *s++;
					while(len<field_width--)
						*str++ = ' ';
					break;
				case'o':
					str=number(str,va_arg(args,unsigned long),8,field_width,precision,flags);
					break;
				case'p':
					if(field_width==-1){
						field_width = 8;
						flags|=ZEROPAD;
					}
					str = number(str,(unsigned long)va_arg(args,void*),16,field_width,precision,flags);
					break;
				case'x':
					flags|=SMALL;
				case'X':
					str = number(str, va_arg(args,unsigned long),16,field_width,precision,flags);
					break;
				case'd':
				case'i':
					flags|=SIGN;
				case'u':
					str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
					break;
				case'n':
					ip = va_arg(args, int*);
					*ip = (str-buf);
					break;
				default:
					if(*fmt!='%')
						*str++ = '%';
					if(*fmt)
						*str++ = *fmt;
					else 
						fmt--;
					break;
			}
	}
	*str = '\0';
	return str-buf;
}
static char buf[1024];
int printk(const char *fmt, ...)
{
	va_list args;
	int i;
	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);
	
	print(buf);
	return i;
}
int user_print(const char *);
int printf(const char *fmt, ...)
{
	va_list args;
	int i;
	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);
	user_print(buf);
	return i;
}

int cprintk(char color, const char *fmt, ...)
{
	va_list args;
	int i;
	char old_color = bDispColor;
	
	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);
	
	bDispColor = color;
	print(buf);
	bDispColor = old_color;
	return i;
}
int xycprintk(int x, int y, char color, const char *fmt, ...)
{
	va_list args;
	int i;
	unsigned long old_pos = dwDispPos;
	char old_color = bDispColor;
	
	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);
	
	dwDispPos = x + y*160;
	bDispColor = color;
	print(buf);
	bDispColor = old_color;
	dwDispPos = old_pos;
	return i;
}

